// main.cpp
// Compile like this: g++ -O2 -Wall -std=c++0x main.cpp matrix_element_trait.cpp reference_counter.cpp -o main
// For valgrind: g++ -O0 -g -Wall -std=c++0x main.cpp matrix_element_trait.cpp reference_counter.cpp -o main

#include <iostream>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <sstream>

#include <limits.h>
#include <cmath>
#include "matrix.h"
#include "new_type.h"

namespace derp {
using namespace std;

void fail()
{
    cout << "Fail" << endl;
    abort();
}

template<typename T>
void test_invertable(size_t dim)
{
    Matrix<T> a = Matrix<T>::RandomMatrix(dim);
    Matrix<T> b = a.Invert();
    Matrix<T> c = a*b;
    if(!c.IsIdentity())
    {
        cout << c << endl;
        fail();
    }
    a.Swap(b);
    a.Swap(a);

    a.Swap(c);
    if(!a.IsIdentity())
    {
        cout << a << endl;
        fail();
    }
    if(c.IsIdentity())
    {
        cout  << "This is PROBABLY an error, but not necessarily\n" << c << endl;
        fail();
    }

    try
    {
        Matrix<T> d = Matrix<T>::RandomMatrix(dim + 1);
        a *= d;
        fail();
    }
    catch(MatrixException & e) { }
    if(!a.IsIdentity())
    {
        cout << a << endl;
        fail();
    }

    try
    {
        Matrix<T> d = Matrix<T>::RandomMatrix(dim + 1);
        a += d;
        fail();
    }
    catch(MatrixException & e) { }
    if(!a.IsIdentity())
    {
        cout << a << endl;
        fail();
    }
}

void test_invertable_int(size_t dim)
{
    Matrix<int> a = Matrix<int>::RandomMatrix(dim);
    // Uncomment the following line to see that it will not compile
    //Matrix<int> b = a.Invert();
}

template<typename T>
void test_multi(size_t dim)
{
    Matrix<T> a = Matrix<T>::Identity(dim);
    Matrix<T> b = a * 10; // multiplication with integer
    Matrix<T> c = b * 0.1; // multiplication with double
    Matrix<T> d = 0.1 * b; // The other way around
    if(!a.IsIdentity())
    {
        cout << a << endl;
        fail();
    }
    if(b.IsIdentity())
    {
        cout << b << endl;
        fail();
    }
    if(!c.IsIdentity())
    {
        cout << c << endl;
        fail();
    }
    if(c != d)
    {
        cout << d << endl;
        fail();
    }

    b *= 0.1;
    if(!b.IsIdentity())
    {
        cout << b << endl;
        fail();
    }

    a *= b;
    a += b;
}

template<typename T>
void test_copy_on_write(size_t dim)
{
    Matrix<T> a(dim);
    Matrix<T> b(dim);

    // a and b should have two different internal pointers
    if(a.GetInternal() == b.GetInternal())
    {
        fail();
    }

    Matrix<T> c = a;
    b = c;
    
    // a, b, and c should have all contain the same internal pointers
    if(a.GetInternal() != b.GetInternal() || 
        a.GetInternal() != c.GetInternal())
    {
        fail();
    }

    Matrix<T> d = a + b + c;
    std::stringstream ss;
    ss << a << b << c;

    // Does not work, calls wrong "non-const" operator
    //double temp = a(0,0) + b(1,1);

    // So long as we read, no copying should occur
    if(a.GetInternal() != b.GetInternal() || 
        a.GetInternal() != c.GetInternal())
    {
        fail();
    }

    a = d;
    a += b + c;
    
    // a should have new internal, while b and c still are the same
    if(a.GetInternal() == b.GetInternal() || 
        a.GetInternal() == d.GetInternal() || 
        b.GetInternal() != c.GetInternal())
    {
        fail();
    }
}
}

int main(int argc, char *argv[])
{
using namespace std;
using namespace derp;
#ifdef _DEBUG
    argc = 6;
    char * temp = argv[0];
    char ** it = argv = new char*[argc];
    *(it++) = temp;
    *(it++) = "1";
    *(it++) = "2";
    *(it++) = "3";
    *(it++) = "4";
    *(it++) = "5";
#endif // _DEBUG
    if (argc < 2)
    {
        cerr << "Usage: " << argv[0] << " size\n";
        return 1;
    }
    else
    {
        try
        {
            for(int j = 1 ; j < argc ; ++j)
            {
                int i = atoi(argv[j]);
                test_invertable<double>((size_t) i);
                test_invertable<float>((size_t) i);
                test_invertable_int((size_t) i);
                test_invertable<NewType>((size_t) i);

	            test_multi<double>((size_t) i);
	            test_multi<float>((size_t) i);
	            test_multi<int>((size_t) i);
	            test_multi<NewType>((size_t) i);

                test_copy_on_write<double>((size_t) i);
                test_copy_on_write<float>((size_t) i);
                test_copy_on_write<int>((size_t) i);
                test_copy_on_write<NewType>((size_t) i);
            }
            cout << "Test passed.\n";
        }
        catch(...)
        {
            fail();
        }
        
#ifdef _DEBUG
        cin.get();
#endif // _DEBUG
        return 0;
    }
}
